/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.util;

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.zip.GZIPOutputStream;

import javax.swing.JTable;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

/**
 * TODO Add BasicUtilities Overview JavaDoc.
 * <p>
 * 
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @version 1.0
 */
public class BasicUtilities {

    private BasicUtilities() {

    }

    public static int getCommandMask() {

        Toolkit toolkit = Toolkit.getDefaultToolkit();
        return toolkit.getMenuShortcutKeyMask();
    }

    /**
     * Creates a tab delimited text for selected cells of the given JTable.
     * <p>
     * 
     * @param table to copy selection from.
     */
    public static void copySelectedCellsToClipBoard(JTable table) {

        if (table != null) {
            try {
                table.getCellEditor().cancelCellEditing();
            } catch (Throwable t) {
            }
            StringBuffer buff = new StringBuffer("");
            StringBuffer row = new StringBuffer("");
            for (int r = 0; r < table.getRowCount(); r++) {
                for (int c = 0; c < table.getColumnCount(); c++) {
                    if (table.isCellSelected(r, c))
                        row.append(table.getValueAt(r, c) + "\t");
                }
                if (row.toString().trim().length() >= 1) {
                    buff.append(row);
                    buff.append(System.getProperty("line.seperator", "\n"));
                }
                row.setLength(0);
            }

            Toolkit tk = Toolkit.getDefaultToolkit();
            Clipboard cb = tk.getSystemClipboard();
            StringSelection ss = new StringSelection(buff.toString().trim());
            cb.setContents(ss, ss);
        }
    }

    /**
     * Utilitiy method to restore System.out to the default FileDescriptor.
     * <p>
     */
    public static void restoreSystemOut() {

        PrintStream out = new PrintStream(new FileOutputStream(FileDescriptor.out));
        System.setOut(out);
    }

    /**
     * Utilitiy method to restore System.err to the default FileDescriptor.
     * <p>
     */
    public static void restoreSystemErr() {

        PrintStream out = new PrintStream(new FileOutputStream(FileDescriptor.err));
        System.setErr(out);
    }

    /**
     * Helper method for checking if the current thread is interrupted without resetting the interrupted state.
     * <p>
     * 
     * @return <tt>true</tt> if the calling thread is interrupted.
     */
    public static boolean isThreadInterrupted() {

        return Thread.currentThread().isInterrupted();
    }

    /**
     * Creates a generally safe file name.
     * <p>
     * This method will hopefully prevent silly errors that can occur by attempting to create a file with illegal
     * characters. This method more or less takes all the ASCII symbols and converts them to underscores.
     * <p>
     * This method more or less is a culmination of what i know to be invalid and or annoying characters to have in a
     * file name on the systems i know.
     * <p>
     * For *NIX users in paticular so extra precautions are made so that the names of these files will be easier to deal
     * with in shell scripts such that there aren't many characters that will make them difficult when dealing within a
     * given shell, and shouldn't have to resorting to alot of escape sequences.
     * 
     * @param fqFilename original filename.
     * @return String an updated version that should be safe for most systems.
     */
    public static String createSafeFilename(final String fileName) {

        String fqFilename = fileName;
        fqFilename = fqFilename.replace(File.pathSeparatorChar, '_');
        fqFilename = fqFilename.replace(File.separatorChar, '_');
        fqFilename = fqFilename.replace('*', '_');
        fqFilename = fqFilename.replace(' ', '_');
        fqFilename = fqFilename.replace('(', '_');
        fqFilename = fqFilename.replace(')', '_');
        fqFilename = fqFilename.replace('\'', '_');
        fqFilename = fqFilename.replace('|', '_');
        fqFilename = fqFilename.replace('\"', '_');
        fqFilename = fqFilename.replace(';', '_');
        fqFilename = fqFilename.replace(':', '_');
        fqFilename = fqFilename.replace('>', '_');
        fqFilename = fqFilename.replace('<', '_');
        fqFilename = fqFilename.replace('[', '_');
        fqFilename = fqFilename.replace(']', '_');
        fqFilename = fqFilename.replace('&', '_');
        fqFilename = fqFilename.replace('#', '_');
        fqFilename = fqFilename.replace('@', '_');
        fqFilename = fqFilename.replace('$', '_');
        fqFilename = fqFilename.replace('%', '_');
        fqFilename = fqFilename.replace('^', '_');
        return fqFilename;
    }

    /**
     * Utility method from moving an inputstream to an outputstream.
     * <p>
     * Pretty basic stuff here every byte read from in is immediately written to out.
     * 
     * @param in InputStream to read from.
     * @param out OutputStream to write to.
     * @throws IOException if error occurs while write
     */
    public static void copyStream(InputStream in, OutputStream out) throws IOException {

        synchronized (in) {
            synchronized (out) {
                byte[] buffer = new byte[256];
                while (true) {
                    int bytesRead = in.read(buffer);
                    if (bytesRead == -1)
                        break;
                    out.write(buffer, 0, bytesRead);
                }
            }
        }
    }

    /**
     * Utility method from moving an inputstream to an outputstream.
     * <p>
     * Pretty basic stuff here every byte read from in is immediately written to out.
     * 
     * @param in InputStream to read from.
     * @param out OutputStream to write to.
     * @throws IOException if error occurs while write
     */
    public static void copyReader(Reader in, Writer out) throws IOException {

        synchronized (in) {
            synchronized (out) {
                char[] buffer = new char[256];
                while (true) {
                    int bytesRead = in.read(buffer);
                    if (bytesRead == -1)
                        break;
                    out.write(buffer, 0, bytesRead);
                }
            }
        }
    }

    /**
     * Helper method for creating an XML reader for parsing XML documents.
     * <p>
     * 
     * @param namespaceAware sets namespace aware property in the XMLReader.
     * @param validating set the validating property with the XMLReader.
     * @return instance of XMLReader for parsing XML.
     * @throws ParserConfigurationException if the XMLReader fails to instantiate properly.
     * @see SAXParserFactory#setNamespaceAware(boolean)
     * @see SAXParserFactory#setValidating(boolean)
     */
    public static XMLReader createXMLReader(boolean namespaceAware, boolean validating)
            throws ParserConfigurationException {

        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setNamespaceAware(namespaceAware);
        spf.setValidating(validating);
        SAXParser parser = null;
        try {
            parser = spf.newSAXParser();
            return parser.getXMLReader();
        } catch (SAXException se) {
            throw new ParserConfigurationException(se.getMessage());
        }
    }

    /**
     * Utility method for sorting collections.
     * <p>
     * This will sort a collection based on the natural ordering of the objects contained in the collection.
     * 
     * @param collection to be sorted.
     * @see #sortCollection(Collection, Comparator)
     */
    public static void sortCollection(Collection<Object> collection) {

        sortCollection(collection, null);
    }

    /**
     * Utility method for sorting collections.
     * <p>
     * This will sort a collection based on the comparator given. If the given comparator is null then naturall based on
     * the objects will be used.
     * 
     * @param collection to be sorted.
     * @param sorter object that can determine the order of the object in the given collection.
     * @see #sortCollection(Collection)
     */
    public static void sortCollection(Collection<Object> collection, Comparator<Object> sorter) {

        Object[] array = collection.toArray();
        collection.clear();
        if (sorter != null) {
            Arrays.sort(array, sorter);
        } else {
            Arrays.sort(array);
        }
        collection.addAll(Arrays.asList(array));
    }

    /**
     * Wraps an exception including the stack trace elements.
     * <p>
     * 
     * @param sourceError that needs to be wrapped.
     * @param wrappedError new error that is a facade of the sourceError.
     */
    public static void wrapThrowable(Throwable sourceError, Throwable wrappedError) {

        if (sourceError == wrappedError || (sourceError == null || wrappedError == null)) {
            return;
        }

        wrappedError.setStackTrace(sourceError.getStackTrace());
        if (sourceError.getCause() != sourceError) {
            try {
                wrappedError.initCause(sourceError.getCause());
            } catch (IllegalStateException ise) {
                // cause already set.
            }
        }
    }

    /**
     * Backup a specific file to another file using compression.
     * <p>
     * This will copy the contents of baseFile to backupFile using a GZIPOutputStream such that it will compressed.
     * 
     * @param baseFile the file to create a backup of.
     * @param backupFile the new destination file for the backup.
     */
    public static void backupFile(File baseFile, File backupFile) throws IOException {

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(baseFile);
            fos = new FileOutputStream(backupFile);
            GZIPOutputStream gzos = new GZIPOutputStream(fos);
            copyStream(fis, gzos);
            gzos.finish();
            gzos.flush();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (Throwable t) {
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (Throwable t) {
                }
            }
        }
    }

    /**
     * Deletes a directory including all childern.
     * <p>
     * One small problem that is easily overlooked in Java is that you cannot delete a non-empty directory.
     * <p>
     * This method will recursivlely scan the directory for other directories and files and proceed to delete them. This
     * method should be executed with caution as it provides no safeguards if a directory is accidently deleted.
     * 
     * @param directory
     * @return <tt>true</tt> if the directory was successfully deleted.
     */
    public static boolean deleteDirectory(File directory) {

        if (directory.isDirectory()) {
            String[] children = directory.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDirectory(new File(directory, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        return directory.delete();
    }
}
